#!/usr/bin/python3
import os
import shutil
import json
import subprocess
from typing import Iterable
from lxml import etree
from base import http, replase
from obs import OpensuseObsAPI
from settings import PROJECT_FOLDER, ACCOUNT
from install import InstallRequires


class KojiRpm:
    """
    koji.fedoraproject中查找src.rpm
    """

    host = "https://koji.fedoraproject.org/koji"

    def _get_source_rpm(self, url):
        response = http.get(url)
        if response.status_code != 200:
            return None
        etree_element = etree.HTML(response.text)
        try:
            td = etree_element.xpath("//*[@id='content']//td[@class='container']")[1]
            return td.xpath(".//tr[2]/td[2]/a[2]/@href")[0]
        except IndexError:
            return None

    def find_rpm(self, rpm):
        """
        解析fedora koji中的数据
        """
        rpm = replase(rpm)
        response = http.get(
            self.host
            + "/search?match=glob&type=package&terms={package}".format(package=rpm),
            timeout=30,
        )
        if response.status_code != 200:
            return None
        etree_element = etree.HTML(response.text)
        try:
            search_content = etree_element.xpath("//div[@id='content']")[0]
            build_url = self._get_build_url(search_content)
        except AttributeError:
            return
        if build_url:
            return self._get_source_rpm(url=build_url)

        # 分页获取
        try:
            package_url = search_content.xpath("./h4[1]/a/@href")[-1]
            total_page = search_content.xpath(
                ".//tr[3]//form[@class='pageJump']//option[last()]/text()"
            )[0]
        except (IndexError, AttributeError):
            return

        for page in range(1, int(total_page)):
            build_url = self.__package_infos(package_url, page)
            if build_url:
                return self._get_source_rpm(url=build_url)

    def _get_build_url(self, element):
        """
        提取url的地址
        """
        for _tr in element.xpath(
            ".//tr[3]//table[contains(@class,'data-list')]//tr[@class='row-odd' or @class='row-even']"
        ):
            if not _tr.xpath('./td[@class="complete"]'):
                continue
            return self.host + "/" + _tr.xpath("./td[1]/a/@href")[-1]

    def __package_infos(self, package_url, page):
        """
        爬取其他页的内容，查找适合的包
        """
        url = (
            self.host
            + "/"
            + package_url
            + "&buildOrder=-completion_time&tagOrder=name&tagStart=0&buildStart={start_page}"
        )
        response = http.get(url.format(start_page=page * 50))
        if response.status_code != 200:
            return None
        search_content = etree.HTML(response.text).xpath("//div[@id='content']")
        if search_content:
            return self._get_build_url(search_content[0])


class FindrpmService:
    """
    find rpm 查找组件对应的源码包
    """

    host = "http://rpmfind.net"

    def _extract_url(self, provide, version):
        response = http.get(
            "http://rpmfind.net/linux/rpm2html/search.php?query={provide}&submit=Search+...&system=&arch=".format(
                provide=provide
            )
        )
        if response.status_code != 200:
            return None

        etree_html = etree.HTML(response.text)
        for _tr in etree_html.xpath("//tr[@bgcolor='#b0ffff']"):
            try:
                pkg_name = _tr.xpath(".//a/text()")[0]
                pkg_url = _tr.xpath(".//a/@href")[0]
            except IndexError:
                return None
            if version and version not in pkg_name:
                continue
            return self.host + pkg_url

    def _koji(self, rpm):
        rpm = rpm[0].split(":")[-1].strip()
        try:
            download = KojiRpm().find_rpm(rpm)
        except Exception:
            download = None
        return rpm, download

    def find_source_rpm(self, provide, version=None):
        """
        查找src-rpm包
        """
        package_url = self._extract_url(provide=provide, version=version)
        rpm, download = None, None
        if not package_url:
            return rpm, download
        response = http.get(package_url)
        etree_html = etree.HTML(response.text)
        try:
            rpm = etree_html.xpath(
                "//table[@align='center']/tbody/tr[5]/td[2]/a/text()"
            )[0]
            download = etree_html.xpath(
                "//table[@align='center']/tbody/tr[5]/td[2]/a/@href"
            )[0]
        except IndexError:
            rpm = etree_html.xpath("//table[@align='center']/tbody/tr[5]/td[2]/text()")
            if rpm:
                return rpm[0].split(":")[-1].strip()
                # return self._koji(rpm)
        return rpm, download


class Command:
    """
    dnf 相关命令的执行
    """

    def __init__(self, package, path=None) -> None:
        self.package = package
        self._save_path = path
        if not os.path.exists(self._save_path):
            os.makedirs(self._save_path, exist_ok=True)

    def download(self):
        """dnf下载源码包"""
        cmd_result = subprocess.run(
            args=["dnf", "download", self.package, "--source"],
            shell=False,
            cwd=self._save_path,
        )
        return cmd_result.returncode == 0

    def _unrpm(self, del_source=True):
        cmd_result = subprocess.run(
            args=["unrpm", self.package + "*.src.rpm"], shell=False, cwd=self._save_path
        )
        if del_source and cmd_result.returncode == 0:
            os.system(
                "rm -rf %s" % os.path.join(self._save_path, self.package + "*.src.rpm")
            )
        return os.listdir(self._save_path)

    def _osc_project(self, project, project_path, clean=True, package=None):
        if clean:
            content = '<project name="%s"></project>' % project
        else:
            content = """
                    <project name="{project}">
                        <package name="{package}" state="A" />
                    </project>
            """.format(
                project=project, package=package
            )
        if not os.path.exists(project_path + "/.osc/_packages"):
            raise FileNotFoundError("project dictory not found ")
        with open(project_path + "/.osc/_packages", "w", encoding="utf-8") as file:
            file.write(content)

    def _osc_remove(self, project_path, file):
        cwd = os.path.join(project_path, file)
        subprocess.run(args=["osc", "addremove"], shell=False, cwd=cwd)
        subprocess.run(args=["osc", "ci", "-n"], shell=False, cwd=cwd)

    def _del_files(self, path):
        if os.path.exists(path):
            os.system("rm -rf %s" % path)

    def osc_upload(self, project_path, project):
        """osc的上传"""
        try:
            self._osc_project(project=project, project_path=project_path)
        except FileNotFoundError:
            print("本地工程路径存在错误:%s" % project_path)
            return
        # 获取rpm包，解压上传
        for file in os.listdir(project_path):
            if os.path.isdir(file):
                continue
            if not file.endswith("src.rpm"):
                continue
            file_name = replase(file)
            self._del_files(path=os.path.join(project_path, file_name))

            # 执行导入和解压缩的命令
            subprocess.run(
                args=["osc", "importsrcpkg", file, "-n", file_name],
                shell=False,
                cwd=project_path,
            )
            try:
                self._osc_remove(project_path=project_path, file=file_name)
            except FileNotFoundError:
                print("更新失败了：%s" % file)
            finally:
                try:
                    os.remove(os.path.join(project_path, file))
                except IOError:
                    print("文件删除失败，请手动删除:%s" % os.path.join(project_path, file))

    def pull_obs_project(self, project, path):
        """拉取特定的工程"""
        cmd_result = subprocess.run(args=["osc", "co", project], shell=False, cwd=path)
        return cmd_result.returncode == 0

    @staticmethod
    def provides_source(provide):
        """
        获取组件对应的源码包
        """
        binary_package = subprocess.check_output(
            args=["dnf", "repoquery", "--whatprovides", provide], shell=False
        ).decode("utf-8")
        if not binary_package:
            return provide, None
        binary_package = binary_package.split("\n")[0]
        source_package = subprocess.check_output(
            args=["dnf", "repoquery", "--source", binary_package], shell=False
        ).decode("utf-8")

        return provide, source_package

    @staticmethod
    def wget(folder, url):
        """
        通过wget下载
        """
        os.system("wget -P %s -nc %s" % (folder, url))


class ObsTools:
    """
    obs自动化操作工具
    """

    def __init__(self) -> None:
        self.obs = OpensuseObsAPI()
        self._find_rpm = FindrpmService()

    def build(self, package, project):
        """obs工程或单个的软件包编译"""
        if self.obs.build(project=project, package=package):
            print("已触发obs的编译操作")
        else:
            print("触发obs软件包编译失败，请稍后重试")

    def depend(self, project, file=None, rpm=None, unresolvable=False, provides=None):
        """
        解决obs中的unresolvable状态的软件包
        """
        rpms = list()
        # 1. 获取这个工程下所有
        if file and os.path.exists(file):
            with open(file, "r", encoding="utf-8") as f:
                rpms.extend(f.readlines())
        if unresolvable:
            _packages = self.obs.get_unresolvable(project_name=project)
            if _packages is None:
                print("获取工程下的unresolvable状态的rpm包时，网络发生异常")
                return

            if rpm:
                rpms.extend(_packages.get(rpm, []))
            else:
                rpms.extend(
                    [
                        pkg.split()[0]
                        for _, provides in _packages.items()
                        for pkg in provides
                    ]
                )
        if provides and isinstance(provides, Iterable):
            rpms.extend(provides)

        if not rpms:
            print("请输入需要导入的rpm包")
            return
        # 2. 下载每一个软件包并添加上传
        rpms = [Command.provides_source(rpm) for rpm in set(rpms)]

        for provide, rpm in set(rpms):
            path = None
            if not rpm:
                try:
                    result = self._find_rpm.find_source_rpm(provide=provide)
                    if isinstance(result, str):
                        rpm = result
                    else:
                        rpm, url = result
                        if not url:
                            raise ValueError()
                        Command.wget(os.path.join(PROJECT_FOLDER, project), url)
                        path = os.path.join(PROJECT_FOLDER, project, rpm)
                except ValueError:
                    continue

            rpm = replase(rpm.replace("\n", ""))
            self.add_package(package=rpm, project=project, path=path)
        self.build(project=project, package=None)

    def _download(self, package, path):
        cmd = Command(package=package, path=path)
        return cmd.download()

    def add_package(self, package, project=None, path=None, build=False):
        """
        新增软件包并上传相关文件
        """
        if project is None:
            project = "home:" + ACCOUNT + ":" + package
        if not self.obs.exists_project(project=project):
            self.obs.create_project(project=project)
        # 工程的拉取
        cmd = Command(package=package, path=PROJECT_FOLDER)
        cmd.pull_obs_project(project=project, path=PROJECT_FOLDER)
        project_path = os.path.join(PROJECT_FOLDER, project)

        if path is None:
            self._download(package=package, path=project_path)
        else:
            if not os.path.exists(path) or os.path.isdir(path):
                print("请传递有效的src.rpm的文件路径")
                return
            if not os.path.exists(os.path.join(project_path, path.split("/")[-1])):
                shutil.copy(path, project_path)

        cmd.osc_upload(project=project, project_path=project_path)
        if build:
            self.build(package=package, project=project)

    def commit(self, package, path):
        """
        提供的软件包名称为spec文件和tar包时，调用此方法推送至obs中编译
        """
        project = "home:gongzt:" + package
        if not self.obs.exists_project(project=project):
            self.obs.create_project(project=project)
        cmd = Command(package=package, path=PROJECT_FOLDER)
        cmd.pull_obs_project(project=project, path=PROJECT_FOLDER)
        project_path = os.path.join(PROJECT_FOLDER, project)

        os.makedirs(os.path.join(project_path, package), exist_ok=True)
        # 文件复制
        for file in os.listdir(path):
            shutil.copyfile(
                os.path.join(path, file), os.path.join(project_path, package, file)
            )

        subprocess.run(
            args=["osc", "add", os.path.join(project_path, package)],
            shell=False,
            cwd=project_path,
        )
        cmd._osc_remove(project_path=project_path, file=package)

    def install(self, project, package=None, csv_file=None, arch="aarch64"):
        """
        软件包安装验证
        """
        if csv_file is None:
            csv_file = None
        install_requires = InstallRequires(arch=arch)
        install_requires.install(project=project, csv_file=csv_file, package=package)

    def extract_json(self, file, packages, build=False, install=False, out_file=None):
        """
        提取json文件中重要的信息
        """
        if not os.path.exists(file):
            raise FileNotFoundError("The specified file does not exist :%s" % file)

        try:
            with open(file, "r", encoding="utf-8") as f:
                check_result = json.loads(f.read())
        except json.JSONDecodeError:
            raise ValueError("This is not a standard JSON file : %s" % file)
        provides = list()
        for pakcage, values in check_result.items():
            if packages and pakcage not in packages:
                continue
            try:
                if build:
                    provides.extend(values["build"]["provides"])
                if install:
                    provides.extend(values["install"]["provides"])
            except KeyError:
                continue
        try:
            with open(out_file, "w", encoding="utf-8") as f:
                f.write("\n".join(set(provides)))
        except IOError:
            raise ValueError("Error writing file output : %s" % out_file)


if __name__ == "__main__":
    tools = ObsTools()
    tools.extract_json(
        file="/home/gzt/obs-tools/t.json",
        packages=[],
        install=True,
        out_file="/home/gzt/obs-tools/res.txt",
    )
    # packages = [
    #     "sonatqube",
    #     "springfrawork",
    #     "react",
    #     "shiro",
    #     "solr",
    #     "mozjs",
    #     "OpenFOAM",
    #     "ltp",
    #     "minio",
    #     "VUE",
    #     "iscsi-initiator-utils-iscsiuio",
    #     "Lammps",
    #     "controller-manager",
    #     "ingresscontroller",
    #     "kubelet",
    #     ".NetCore",
    #     "blackbox_exporter",
    #     "brpc",
    #     "dbeaver",
    #     "gatk",
    #     "gerrit",
    #     "glassfish",
    #     "home-assistant",
    #     "janusgraph",
    #     "mitmproxyNA",
    #     "mozjs",
    #     "openhpc",
    #     "swagger",
    #     "tdengineLatest",
    #     "tez",
    #     "tornado",
    #     "webhook",
    #     "wildfly",
    #     "zeppelin",
    #     "zeronet",
    #     "audit-libs-python",
    #     "clickhouse",
    #     "pd-server",
    #     "isa-l",
    #     "alluxio",
    #     "accumulo",
    #     "mongoose",
    #     "hue",
    #     "zeppelin",
    #     "phoenix",
    #     "tdengine",
    #     "lstio",
    #     "ats",
    #     "sqoop",
    # ]
    # for package in packages:
    #     tools.add_package(package=package)
